package juc;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.stream.IntStream;

/**
 * ReadWriteLock接口 及 ReentrantReadWriteLock类测试
 * 
 * 读写锁的饥饿写问题：
 * 读线程的频率大于写线程的频率，导致锁长期被读线程霸占，写线程无法获取对数据进行写操作的
 * 权限，从而进入饥饿的状态。
 * 
 * 读写锁的性能问题：
 * 相对于synchronized、可重入锁，读写锁的性能较差（JMH评测）。
 * @author ben
 * @date 2021-09-09 21:19:59 CST
 */
public class TestReadWriteLock {

	public static void main(String[] args) {
		test1();
	}
	
	/**
	 * 简单试验1
	 * 多个线程，分别执行共享数据的 添加、移除、获取 三种操作
	 * @author ben
	 * @date 2021-09-09 21:24:14 CST
	 */
	public static void test1() {
		TestOne test = new TestOne();
		
		ExecutorService es = Executors.newFixedThreadPool(10);
		
		// add、take会影响共享list的数据量，导致其没有数据，需要合理调整休眠时间
		// 必要的话，可以让add先执行几次，且保证 add速度大于take速度
		
		// 线程执行add
		IntStream.range(0, 1).forEach(item->{
			es.execute(()->{
				// 执行100次添加
				IntStream.range(100, 200).forEach(iitem->{
					test.add(item+ "-iitem-" + iitem);
					try {
						// 休眠
						TimeUnit.SECONDS.sleep(5);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				});
			});
		});
		
		// 线程执行take
		IntStream.range(0, 1).forEach(item->{
			es.execute(()->{
				while (true) {
					// 不停take
					String tname = Thread.currentThread().getName();
					System.out.println(tname + ": take=" + test.take());
					
					try {
						// 休眠
						TimeUnit.SECONDS.sleep(8);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		});
		
		// 线程执行take
		IntStream.range(0, 1).forEach(item->{
			es.execute(()->{
				while (true) {
					// 不停get
					String tname = Thread.currentThread().getName();
					// 只取第一个（从0开始）
					// 没有数据时返回null
					System.out.println(tname + ": get=" + test.get(0));
	
					try {
						// 休眠
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		});
	}

}

/**
 * 试验1：一个读写锁，得到一个读锁、一个写锁
 * @author ben
 * @date 2021-09-09 21:24:45 CST
 */
class TestOne {
	
	// 正式日志
	private static Consumer<Object> cs = System.out::println;
	// 测试日志：改变后可以什么都不输出
//	private static Consumer<Object> dbg = System.out::println;
	private static Consumer<Object> dbg = item->{};
	
	// 非公平锁
	private final ReadWriteLock rwl = new ReentrantReadWriteLock();
	// 获得写锁
	private final Lock wl = rwl.writeLock();
	// 获得读锁
	private final Lock rl = rwl.readLock();
	
	// 共享数据：LinkedList可以对 头部、尾部 进行处理
	private final LinkedList<String> list = new LinkedList<>();
	
	/**
	 * 添加到末尾：使用写锁
	 * @author ben
	 * @date 2021-09-09 21:29:50 CST
	 * @param element
	 */
	public void add(String element) {
		String tname = Thread.currentThread().getName();
		String mn = "add";
		dbg.accept(tname + "-" + mn + ": start, list=" + list.size());
		
		wl.lock();
		dbg.accept(tname + "-" + mn + ": 1 wl.lock");
		try {
			// Last!
			list.addLast(element);
			cs.accept(tname + "-" + mn + ": addLast");
		} finally {
			wl.unlock();
			dbg.accept(tname + "-" + mn + ": 2 wl.unlock, list=" + list.size());
		}
	}
	
	/**
	 * 获取并移除头部元素：使用写锁
	 * @author ben
	 * @date 2021-09-09 21:30:28 CST
	 * @return
	 */
	public String take() {
		String tname = Thread.currentThread().getName();
		String mn = "take";
		dbg.accept(tname + "-" + mn + ": start");
		
		wl.lock();
		dbg.accept(tname + "-" + mn + ": 1 wl.lock");
		try {
			// First!
			cs.accept(tname + "-" + mn + ": before removeFirst=" + list.size());
			return list.removeFirst();
		} catch (NoSuchElementException e) {
			cs.accept("take发生异常: e=" + e.getMessage());
			return null;
		} finally {
			wl.unlock();
			dbg.accept(tname + "-" + mn + ": 2 wl.unlock, list=" + list.size());
		}
	}
	
	/**
	 * 读取指定位置的数据：使用读锁
	 * @author ben
	 * @date 2021-09-09 21:33:21 CST
	 * @param index
	 * @return 下表错误返回null
	 */
	public String get(int index) {
		String tname = Thread.currentThread().getName();
		String mn = "get";
		dbg.accept(tname + "-" + mn + ": start, list=" + list.size());
		
		rl.lock();
		dbg.accept(tname + "-" + mn + ": 1 rl.lock");
		try {
			cs.accept(tname + "-" + mn + ": before get");
			return list.get(index);
		} catch (IndexOutOfBoundsException e) {
			cs.accept("get发生异常：index=" + index + ", e=" + e.getMessage());
			return null;
		} finally {
			rl.unlock();
			dbg.accept(tname + "-" + mn + ": 2 rl.unlock, list=" + list.size());
		}
	}
	
}
